package com.cskaoyan.javase.object._4hashcode;

/**
 * hashCode方法
 * 1.方法的声明:
 *      public native int hashCode();
 *      是一个本地方法,方法不需要参数,但是会返回一个int整数.
 *
 * 2.方法的作用:
 * 由于它是一个本地方法，
 * 我们通过查阅JDK文档，文档中关于它的作用说明了两点：
 *      1.返回该对象的哈希码值(哈希值,散列值)。
 *          哈希映射,指的是将无限元素集合中的元素,映射到有限元素集合中的元素."化无限为有限"
 *          哈希映射不要求"一一对应",可以出现"多对一",但决不允许出现"一对多"
 *          哈希映射中一旦出现"多对一"称之为"哈希冲突"(哈希冲突不可避免,只可能尽量减少)\
 *
 *          Java中的哈希映射,映射的规则,映射的对应关系就是类中的hashCode方法
 *          该方法将一个无限的集合(某个类对象的集合)
 *          映射到一个有限的集合(int数据类型是有限的)
 *          在Java中hashCode方法的返回值(一个int整数),就是该对象的哈希码值,哈希值.
 *
 *      2.支持此方法是为了提高哈希表（例如 java.util.Hashtable 提供的哈希表）的性能。
 *          哈希表中一旦出现"哈希冲突"就会导致哈希表的性能下降
 *          所以设计出更好的哈希算法,减少哈希冲突,可以提升哈希表的性能
 *
 *  在上述案例中,hashCode方法的作用,是为了支持集合当中的哈希表的实现
 *  但是哈希表的使用,不光光需要hashCode方法支持,还需要equals方法
 *
 *  一旦equals方法认定对象相等,那么这两个对象就是集合A中的同一个对象,那么它们的哈希值必须相同
 *  一旦equals方法认定对象不相等,那么这两个对象就是集合A中的两个对象,那么它们的哈希值可以不相等,
 *      也可以相等(哈希冲突)
 *
 *
 * 3.Object类默认实现
 * Object默认的hashCode方法，是一个本地方法，
 * 我们没办法直接看代码分析方法的实现。
 * 在JDK文档中，是这么描述的（原话）：
 *
 *      实际上，由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。（这一般是通过将该对象的内部地址转换成一个int整数来实现的，但是 Java 编程语言不需要这种实现技巧。）
 *
 * 这说明什么呢？
 * 默认情况下，哈希值是通过计算对象的地址值来获取的。
 * 这样的话，一般两个独立的对象的哈希值是不同的，而同一个对象（用两个引用调用方法）它们的哈希值必然是相同的。
 *
 * 怎么判断是同一个对象?
 * 要通过类中的equals方法,返回true说明是同一个对象,false说明不是
 *
 * 在没有重写equals方法和hashCode方法的时候,它们都是根据对象的地址来判断对象相等和计算哈希值的
 * 这样这两个方法就具有一致性:
 *      一旦equals方法认定对象相等,那么它们的哈希值必须相同(因为地址值相等)
 *      一旦equals方法认定对象不相等,那么它们的哈希值可以不相等,也可能相等.
 *      这样才满足哈希映射,映射的定义
 *
 *
 * 4.如果重写了equals方法,为了能够满足映射定义,可以不重写hashCode方法吗?
 *      不可以
 *      equals方法和hashCode方法,要么都不重写,要么都同时重写,而且重写的依据,应该是一模一样的
 *
 *
 * 5.具体如何重写hashCode方法呢?
 * 重写hashCode方法，JDK文档中给我们标注了重写的规则和什么时候进行重写，如下：
 *      1.在 Java 应用程序执行期间，在对同一对象多次调用 hashCode 方法时，必须一致地返回相同的整数
 *      前提是将对象进行 equals比较时所用的信息没有被修改。
 *      说明这两个方法的重写,要看equals方法是否被重写.
 *
 *      2.如果根据 equals(Object)方法，两个对象是相等的，
 *      那么对这两个对象中的每个对象调用hashCode方法都必须生成相同的整数结果。
 *      因为映射定义不允许出现"一对多"
 *      而equals(Object)方法认定两个对象是相等的，返回true时,就是同一个对象
 *
 *      3.如果根据 equals(java.lang.Object)方法，两个对象不相等，
 *      那么对这两个对象中的任一对象上调用hashCode方法不要求一定生成不同的整数结果。(这是因为哈希冲突)
 *      但是，程序员应该意识到，为不相等的对象生成不同整数结果可以提高哈希表的性能。
 *      减少哈希冲突是我们的追求目标,虽然它永远达不到.
 *
 *      hashCode方法和equals方法要重写必须一起重写，而且重写的依据必须是一样的，
 *      这样才能保证两个方法的结果具有一致性。
 *      比如一个Student类，如果equals方法依据其中的成员变量age和name重写的，
 *      那么hashCode方法也必须要依赖于这两个成员变量重写！！
 *
 *      当然hashCode方法的实现,牵扯到算法实现问题,我们Java程序员对算法不作要求
 *      实际开发中,使用idea默认给出的模板实现就可以了
 *
 * 快捷键:
 *      随着学习加深进度,经常需要看源代码
 *      非常常用的快捷键
 *      前进到鼠标光标下一次的位置: alt + ctrl + 方向右键
 *      回到到鼠标光标上一次的位置: alt + ctrl + 方向左键
 *
 * 注意事项:
 *      1.一旦重写hashCode方法后，默认的toString方法就不会再打印地址值了。
 *      这是因为toString方法，默认调用的是本地方法的hashCode方法获取地址值，重写后不再是地址值了。
 *      这时建议同时重写toString方法
 *      避免导致认识错误
 *
 *      2.如果类中有引用数据类型成员变量，那么就继续调用它的hashCode方法，也就需要重写它的hashCode方法。
 *
 *
 * @since 16:05
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        Student s1 = new Student(18, 100);
        Student s2 = new Student(18, 100);
        Student s3 = s1;
        System.out.println(s1.equals(s2));
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());

        System.out.println(s1.equals(s3));
        System.out.println(s3.hashCode());

        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
    }
}

class Student {
    int age;
    double score;
    String name;

    public Student() {
    }

    public Student(int age, double score) {
        this.age = age;
        this.score = score;
    }

   /* @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        if (age != student.age) return false;
        if (Double.compare(student.score, score) != 0) return false;

        return true;
    }*/

    /*@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return Double.compare(student.score, score) == 0;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }*/

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        if (Double.compare(student.score, score) != 0) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }

}

// 快速写完人生中第一个百行Java代码
class A {
    private int a;
    private int b;
    private int c;
    private int d;
    private int e;
    private int f;

    public A(int a, int b, int c, int d, int e, int f) {
        this.a = a;
        this.b = b;
        this.c = c;
        this.d = d;
        this.e = e;
        this.f = f;
    }

    public A() {
    }

    public A(int a) {
        this.a = a;
    }

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }

    public int getB() {
        return b;
    }

    public void setB(int b) {
        this.b = b;
    }

    public int getC() {
        return c;
    }

    public void setC(int c) {
        this.c = c;
    }

    public int getD() {
        return d;
    }

    public void setD(int d) {
        this.d = d;
    }

    public int getE() {
        return e;
    }

    public void setE(int e) {
        this.e = e;
    }

    public int getF() {
        return f;
    }

    public void setF(int f) {
        this.f = f;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        A a1 = (A) o;

        if (getA() != a1.getA()) return false;
        if (getB() != a1.getB()) return false;
        if (getC() != a1.getC()) return false;
        if (getD() != a1.getD()) return false;
        if (getE() != a1.getE()) return false;
        return getF() == a1.getF();
    }

    @Override
    public int hashCode() {
        int result = getA();
        result = 31 * result + getB();
        result = 31 * result + getC();
        result = 31 * result + getD();
        result = 31 * result + getE();
        result = 31 * result + getF();
        return result;
    }

    @Override
    public String toString() {
        return "A{" +
                "a=" + a +
                ", b=" + b +
                ", c=" + c +
                ", d=" + d +
                ", e=" + e +
                ", f=" + f +
                '}';
    }
}